import os
import sys
import wx

from app.util import pluginBase, files, path

from . import panel_xrc, opkg

def _getImage(name):
    _imgFolder = path.getModulePath("app.plugins.OpkgManager.img")
    return wx.Bitmap(os.path.join(_imgFolder, name + ".png"))

class PkgList(object):
    """package list wrapper around listctrl."""

    _ctrl = None

    _images = {
        "installed": ("installed", _getImage("install")),
        "doDelete": ("delete", _getImage("delete")),
        "notInstalled": ("not installed", _getImage("empty")),
        "doUpgrade": ("upgrade", _getImage("upgrade")),
    }

    _colors = {
        "doUpgrade": wx.Colour(200, 255, 200),
        "doDelete": wx.Colour(255, 200, 200),
    }

    _imgList = None
    _imgIds = None
    _items = None

    def __init__(self, ctrl):
        self._ctrl = ctrl
        self._imgList = wx.ImageList(16, 16)
        self._imgIds = {}
        for (_name, (_action, _img)) in self._images.iteritems():
            _id = self._imgList.Add(_img)
            self._imgIds[_name] = {"action": _action, "id": _id}
        self._ctrl.SetImageList(self._imgList, wx.IMAGE_LIST_SMALL)
        self._setColumns()

    def _setColumns(self):
        _item = wx.ListItem()
        _item.m_mask = wx.LIST_MASK_TEXT | \
            wx.LIST_MASK_IMAGE | \
            wx.LIST_MASK_FORMAT
        _item.m_image = -1
        _item.m_format = 0
        _item.m_text = "State"
        self._ctrl.InsertColumnInfo(0, _item)
        self._ctrl.InsertColumn(1, "Name", wx.LIST_FORMAT_LEFT)
        self._ctrl.InsertColumn(2, "Description")

    def _addItem(self, item, itemKey):
        if item.status.installed():
            _state = "installed"
        else:
            _state = "notInstalled"
        _state = self._imgIds[_state]
        _index = self._ctrl.InsertImageStringItem(
            sys.maxint, _state["action"], _state["id"])
        self._ctrl.SetStringItem(_index, 1, item.getPackageName())
        self._ctrl.SetStringItem(_index, 2, item.getDescription())
        self._ctrl.SetItemData(_index, itemKey)

    def SetListItems(self, items):
        """Set list items."""
        self._ctrl.ClearAll()
        self._setColumns()
        self._items = tuple(items)
        for (_id, _item) in enumerate(self._items):
            self._addItem(_item, _id)

    def GetSelectedData(self):
        """Return data of selected item"""
        if not self._isReady():
            return None
        _pos = self._ctrl.GetFirstSelected()
        _id = self._ctrl.GetItemData(_pos)
        return self._items and self._items[_id]

    def _isReady(self):
        return self._items and self._ctrl.GetFirstSelected() >= 0

    def setCurState(self, name):
        if not self._isReady():
            return
        _pos = self._ctrl.GetFirstSelected()
        self._ctrl.SetItemBackgroundColour(_pos, self._colors[name])
        self._ctrl.SetItemImage(_pos, self._imgIds[name]["id"])

    def restoreCurToDefault(self):
        if not self._isReady():
            return
        _data = self.GetSelectedData()
        _pos = self._ctrl.GetFirstSelected()
        _data.status.setWantStatus(None)
        _state = "installed" if _data.status.installed() \
            else "notInstalled"
        self._ctrl.SetItemBackgroundColour(_pos,
            self._ctrl.GetBackgroundColour())
        self._ctrl.SetItemImage(_pos, self._imgIds[_state]["id"])

    def markCurInstalled(self, state):
        if not self._isReady():
            return
        if state:
            self.setCurState("doUpgrade")
            _data = self.GetSelectedData()
            _data.status.setWantStatus("upgrade")
        else:
            self.restoreCurToDefault()

    def markCurDeleted(self, state):
        if not self._isReady():
            return
        if state:
            self.setCurState("doDelete")
            _data = self.GetSelectedData()
            _data.status.setWantStatus("delete")
        else:
            self.restoreCurToDefault()

class ManagerPanel(panel_xrc.xrcOpkgManPanel, pluginBase.PluginMixin):

    name = "Package manager"

    _allPackages = None

    _pkgList = None

    def __init__(self, parent, app):
        super(ManagerPanel, self).__init__(parent)
        self._mixinInit(parent, app)
        self._pkgList = PkgList(self.packageList)
        self._initPkgListColumns()

    def _initPkgListColumns(self):
        """Set look of package list"""

    def _readPkgFiles(self):
        """Return file-like objects that contain package info."""
        _p = self._app.executeRemote("cat /etc/opkg.conf  | grep -i lists_dir")
        _p.wait()
        _lists = _p.stdout.read().strip()
        assert len(_lists.splitlines()) == 1, _lists
        _dir = _lists.rsplit(" ", 1)[-1]
        _p =self._app.executeRemote("ls -1 %s" % files.escapeArg(_dir))
        _p.wait()
        _files = _p.stdout.read().splitlines()
        _dir = _dir.rstrip("/")
        _files = ["/".join((_dir, _line.strip()))
            for _line in _files]
        for _file in _files:
            _p = self._app.executeRemote(
                "cat %s" % files.escapeArg(_file), True
            )
            _p.wait()
            yield _p.stdout
        # also yield data with non-standard status
        _p = self._app.executeRemote("cat /usr/lib/opkg/status")
        _p.wait()
        yield _p.stdout

    def _parseFile(self, fobj):
        """Parse single file-like object and yield package infos."""
        _pkgData = {}
        _line = 42
        while _line:
            _line = fobj.readline().decode("utf8")
            if _line.strip() == "":
                # package data is separated by empty lines
                # (one or two of them, actually)
                if _pkgData:
                    yield opkg.OpkgPackage(_pkgData)
                    _pkgData = {}
            elif _line:
                _seenEmptyLines = 0
                (_name, _value) = _line.split(":", 1)
                _name = _name.strip()
                _pkgData[_name] = _value.strip()
            else:
                # EOF
                pass
        if _pkgData:
            yield opkg.OpkgPackage(_pkgData)

    def _loadPackages(self):
        """Locad package list from device to the memory."""
        self._allPackages = {}
        for _fobj in self._readPkgFiles():
            for _info in self._parseFile(_fobj):
                _name = _info.getPackageName()
                if _name in self._allPackages:
                    self._allPackages[_name].update(_info)
                else:
                    self._allPackages[_name] = _info

    def _setToList(self, pkgList):
        """Load package list from memory to GUI list."""
        self._pkgList.SetListItems(pkgList)

    def _setDescribedItem(self, item):
        self.statusToButtons(item)
        self.pkgDesc.SetValue(item.getFullDescription())

    def statusToButtons(self, item):
        if item:
            _inst = item.status.getWantStatus() in ("upgrade", "install")
            _del = item.status.getWantStatus() in ("delete", )
        else:
            _inst = _del = False
        self.installBtn.SetValue(_inst)
        self.delBtn.SetValue(_del)

    def _doOpkgUpdate(self):
        _p = self._app.executeRemote("opkg update", True, {
            "showStdout": "utf8",
            "showStderr": "utf8",
            "allowInput": True,
        })
        _p.wait()

    def _doOpkgUpgrade(self):
        _p = self._app.executeRemote("opkg upgrade", True, {
            "showStdout": "utf8",
            "showStderr": "utf8",
            "allowInput": True,
        })
        _p.wait()

    def _applyChanges(self):
        _install = []
        _delete = []
        for _package in self._allPackages.itervalues():
            _status = _package.status.getWantStatus()
            if _status in ("upgrade", "install"):
                _install.append(_package)
            elif _status in ("delete", ):
                _delete.append(_package)
        _install = map(lambda el: el.getPackageName(), _install)
        _delete = map(lambda el: el.getPackageName(), _delete)
        _windowStatus = {
            "showStdout": "utf8",
            "showStderr": "utf8",
            "allowInput": True,
            "requestInputMatch": lambda data: "?" in data,
        }
        if _install:
            _p = self._app.executeRemote(
                "opkg install " + " ".join(_install), True, _windowStatus)
            _p.wait()
        if _delete:
            _p = self._app.executeRemote(
                "opkg delete " + " ".join(_delete), True, _windowStatus)
            _p.wait()

###########

    def OnButton_fetchPkgList(self, evt):
        """'Refresh package list' button pressed."""
        self._loadPackages()
        self._setToList(self._allPackages.values())

    def OnText_pkgSearch(self, evt):
        """'Do search' textbox contents changed"""
        _text = evt.GetString()
        if len(_text) < 3:
            # too many results. Not really worth it.
            return
        # case-insensetive match
        _text = _text.lower()
        _filteredPackages = []
        for _info in self._allPackages.itervalues():
            if any(map(
                lambda el: _text in unicode(el).lower(),
                _info.iterValues()
            )):
                _filteredPackages.append(_info)
        self._setToList(_filteredPackages)

    def OnList_item_selected_packageList(self, evt):
        _info = self._pkgList.GetSelectedData()
        self._setDescribedItem(_info)

    def OnTogglebutton_installBtn(self, evt):
        self._pkgList.markCurInstalled(evt.Checked())
        self.statusToButtons(self._pkgList.GetSelectedData())

    def OnTogglebutton_delBtn(self, evt):
        self._pkgList.markCurDeleted(evt.Checked())
        self.statusToButtons(self._pkgList.GetSelectedData())

    def OnButton_opkgUpBtn(self, evt):
        self._doOpkgUpdate()
        self._loadPackages()
        self._setToList(self._allPackages.values())

    def OnButton_opkgUpgrBtn(self, ect):
        self._doOpkgUpgrade()
        self._loadPackages()
        self._setToList(self._allPackages.values())

    def OnButton_applyBtn(self, evt):
        self._applyChanges()
        self._loadPackages()
        self._setToList(self._allPackages.values())

# vim: set sts=4 sw=4 et :
